定期ミートアップ 第30回
CarbonとZig、async/await
C++との互換性の高い実験的言語
async/awaitと継続がある?
「限定継続は"一級継続を受け取る、再開可能な例外"みたいなもの」
「限定継続自体を提供したいわけではなく、コルーチン/スレッド/例外等の機能のためのplace-holder」
いつのまにかこれらの機能を足せない状態になっていた、というのを避けたいということかな
code:txt
coroutine
- stackful coroutine
- スタック境界を超えてジャンプできる
- RubyのFiberはこれ
- stackless coroutine
- スタック境界を超えてジャンプできない
- よく「generator」と呼ばれる(eg. JavaScript, Rust)
例
code:rb
def foo
Fiber.yield
end
def bar
foo # ここで止まる
...
end
code:js
async function foo(){
await HTTP.get(this.url)
}
function bar(){
foo() // 止まらない
...
}
async function bar(){
await foo(); // 止めたい場合はこの関数もasyncを付けないといけない
...
}
実現方法
ジェネレータはステートマシンに変換することで実装できる
code:txt
function* foo() {
処理A switch(this.state) {
yield case 0: 処理A; this.state++; return this;
処理B case 1: 処理B; this.state++; return this;
}
stackful coroutineはもっと大変
マシンスタックの保存・復元が必要
特に自作言語からC言語の関数を呼び出せたりする場合。
JSやRustのasync/awaitはジェネレータに変換される(要出典)
が、「ジェネレータがあればasync/awaitが作れる」ではない!
ジェネレータがあれば、自作言語で書かれた関数を並行して動かすことができる
が、async/awaitでやりたいことってネットワークやDBの待ち時間に別の処理をやることで、そうするとマシンスタックの保存・復元が結局必要になる?(ここ自信ない)
それはともかく:
Zigのasync/awaitは上記のいずれでもない
特徴1:関数をasync呼び出しするとframeオブジェクトが返る
関数内にsuspendがあるとそこで止まる
frameはresumeで再開できる
code:zig
var bar: i32 = 1;
test "suspend with resume" {
var frame = async func2(); //1
resume frame; //4
try expect(bar == 3); //6
}
fn func2() void {
bar += 1; //2
suspend {} //3
bar += 1; //5
}
関数が値を返す場合はresumeではなくawaitを使う?(嘘かも)
特徴2:@frameで現在のframeが取れる
これを使うとasync/awaitのスケジューラをZig自身で書くことができる
code:zig
var timer_queue: std.PriorityQueue(Delay, void, cmp) = undefined;
fn waitForTime(time_ms: u64) void {
suspend {
timer_queue.add(Delay{
.frame = @frame(),
.expires = nanotime() + (time_ms * std.time.ns_per_ms),
}) catch unreachable;
}
}
スタックをまるごと保存するので、JSのasync/awaitのようにこれを使う関数にasyncを付けてまわる必要がない